﻿using DataModels.Common;
using DataModels.Enumerations;
using DataModels.Models.OfflineData;
using GenericTools.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Serialization;

namespace GenericTools.OfflineDataCapture
{
    public class OfflineDataCapturing : BaseWebAuthorization
    {
        private static bool offlineIsEnabled = false;
        private static bool offlineStatusLoaded = false;
        private static bool offlineDataSavingEnvironmentInitialized = false;
        private static bool offlineDataSavingEnviromentStatusOK = false;

        private static string locationForSavingOfflineData = @"C:\IQSMS\OfflineData";
        private const string OFFLINE_DATA_FILENAME = "Reports.xml";

        private static OfflineReportCollection pendingOfflineReports;
        private static OfflineReportCollection skippedOfflineReports;
        private static Dictionary<string, OfflineReport> existingPendingOfflineReports;

        private static IQServices.IQService webService;

        public static event EventHandler<OfflineDataCaptureArgs> OfflineDataChanged;

        static OfflineDataCapturing()
        {
            webService = new IQServices.IQService();
            webService.AuthenticationHeaderValue = new IQServices.AuthenticationHeader();
            webService.Url = AppConfiguration.WebServiceURL;
        }

        private static bool IsOfflineDataSavingEnabled()
        {
            if (!offlineStatusLoaded)
            {
                string setting = Utilities.GetSetting(DataModels.Models.SysSetting.SETTING_OFFLINE_DATA_SAVING_ENABLED);
                if (string.IsNullOrWhiteSpace(setting)) offlineIsEnabled = false;
                else
                {
                    bool converted = bool.TryParse(setting, out offlineIsEnabled);
                    if (!converted) offlineIsEnabled = false;

                    // If the value is true, then if this value was retrieved through offline mechanism
                    // we need to check the date of this offline version, so that this exercise is not continuous
                    // We can tolerate old offline settings but for not more than three months.
                    if (offlineIsEnabled)
                    {
                        if (AppConfiguration.AccessMethod != DataAccessMethod.WEB_SERVICES)
                        {
                            DateTime? cacheCreationDate = Utilities.GetOfflineDataDate();
                            if (cacheCreationDate.HasValue)
                            {
                                DateTime cacheDBData = cacheCreationDate.Value;
                                DateTime threeMonthsAgo = DateTime.Now.AddMonths(-3);
                                if (cacheDBData < threeMonthsAgo) return false;
                            }
                            else throw new Exception("You need to have offline cache database for this feature to operate properly. "
                                + "Please download the cache database before proceeding. Use the login window to download the cache database");
                        }
                    }
                }

                offlineStatusLoaded = true;
            }

            return offlineIsEnabled;
        }

        public static bool AnyReportNeedsUploading()
        {
            InitializeOfflineDataSavingEnvironment();
            if (pendingOfflineReports == null) return false;
            else return (pendingOfflineReports.Count > 0);
        }

        public static OfflineReportCollection GetSkippedReports()
        {
            InitializeOfflineDataSavingEnvironment();
            return skippedOfflineReports;
        }

        public static bool AnySkippedReport()
        {
            InitializeOfflineDataSavingEnvironment();
            if (skippedOfflineReports == null) return false;
            else return (skippedOfflineReports.Count > 0);
        }

        /// <summary>
        /// Initialize the environment ready for saving offline data.
        /// </summary>
        /// <returns></returns>
        public static bool InitializeOfflineDataSavingEnvironment()
        {
            if (!offlineDataSavingEnvironmentInitialized)
            {
                bool enabled = IsOfflineDataSavingEnabled();
                if (!enabled) offlineDataSavingEnviromentStatusOK = false;
                else
                {
                    // Check the location where these offline data will be saved
                    try
                    {
                        lock ("ONE_THREAD_FOR_OFFLINE_FILE_ACCESS")
                        {
                            if (!Directory.Exists(locationForSavingOfflineData)) Directory.CreateDirectory(locationForSavingOfflineData);
                            string filename = Path.Combine(locationForSavingOfflineData, OFFLINE_DATA_FILENAME);

                            if (!File.Exists(filename))
                            {
                                File.Create(filename).Close();
                                XmlSerializer serializer = new XmlSerializer(typeof(OfflineReportCollection));
                                TextWriter textWriter = new StreamWriter(filename);
                                var rootElement = new OfflineReportCollection();
                                serializer.Serialize(textWriter, rootElement);
                                textWriter.Close();
                            }

                            using (var streamReader = new StreamReader(filename))
                            {
                                XmlSerializer serializer = new XmlSerializer(typeof(OfflineReportCollection));
                                pendingOfflineReports = (OfflineReportCollection)serializer.Deserialize(streamReader);
                                existingPendingOfflineReports = new Dictionary<string, OfflineReport>();
                                skippedOfflineReports = new OfflineReportCollection();

                                // Here we are creating the dictionary so that later we may warn when duplicate reports
                                // are inserted into the dictionary.
                                foreach (var report in pendingOfflineReports)
                                {
                                    string key = string.Format("{0}|{1}|{2}|{3}", report.FacilityCode, report.ReportId, report.ReportYear, report.ReportMonth);
                                    existingPendingOfflineReports[key] = report;
                                }
                                offlineDataSavingEnviromentStatusOK = true;

                                var args = new OfflineDataCaptureArgs { PendingReports = pendingOfflineReports };
                                if (OfflineDataChanged != null) OfflineDataChanged(null, args);
                            }
                        }
                    }
                    catch
                    {
                        offlineDataSavingEnviromentStatusOK = false;
                    }
                }

                offlineDataSavingEnvironmentInitialized = true;
            }

            return offlineDataSavingEnviromentStatusOK;
        }

        /// <summary>
        /// Add the offline report to the collection of existing offline report ready for saving.
        /// Overwrite parameter has nothing to do with the data in the server, just applicable to the offline data.
        /// </summary>
        /// <param name="report">Offine report object</param>
        /// <param name="overwriteExistingOfflineReport">Overwrite any existing report which is in offline cache.</param>
        /// <returns></returns>
        public static bool AddOfflineReport(OfflineReport report, bool overwriteExistingOfflineReport)
        {
            bool okToContinue = InitializeOfflineDataSavingEnvironment();
            if (okToContinue)
            {
                string key = string.Format("{0}|{1}|{2}|{3}", report.FacilityCode, report.ReportId, report.ReportYear, report.ReportMonth);
                if (existingPendingOfflineReports.ContainsKey(key))
                {
                    if (overwriteExistingOfflineReport)
                    {
                        existingPendingOfflineReports[key] = report;
                        pendingOfflineReports.Add(report);

                        var args = new OfflineDataCaptureArgs { PendingReports = pendingOfflineReports };
                        if (OfflineDataChanged != null) OfflineDataChanged(null, args);
                        return true;
                    }
                    else return false;
                }
                else
                {
                    existingPendingOfflineReports[key] = report;
                    pendingOfflineReports.Add(report);

                    var args = new OfflineDataCaptureArgs { PendingReports = pendingOfflineReports };
                    if (OfflineDataChanged != null) OfflineDataChanged(null, args);
                    return true;
                }
            }
            else
            {
                throw new InvalidOperationException("Offline Data Reporting Initialization Failed");
            }
        }

        /// <summary>
        /// Save the in-memory offline data into the persistent file in the computer.
        /// </summary>
        /// <returns>True if everything went well otherwise false</returns>
        public static bool SaveOfflineData()
        {
            bool okToContinue = InitializeOfflineDataSavingEnvironment();
            if (okToContinue)
            {
                try
                {
                    lock ("SAVING_OFFLINE_DATA")
                    {
                        string filename = Path.Combine(locationForSavingOfflineData, OFFLINE_DATA_FILENAME);
                        XmlSerializer serializer = new XmlSerializer(typeof(OfflineReportCollection));
                        TextWriter textWriter = new StreamWriter(filename);
                        serializer.Serialize(textWriter, pendingOfflineReports);
                        textWriter.Close();
                    }
                    return true;
                }
                catch
                {
                    return false;
                }
            }
            else throw new InvalidOperationException("Offline Data Reporting Initialization Failed");
        }

        public static bool UploadOfflineReportData(bool overwriteExistingData)
        {
            bool okToContinue = InitializeOfflineDataSavingEnvironment();
            if (okToContinue)
            {
                bool atLeastOneReportSaved = false;
                skippedOfflineReports.Clear();
                while (pendingOfflineReports.Count > 0)
                {
                    OfflineReport report = pendingOfflineReports[0];

                    ReportingMessage status;
                    try
                    {
                        status = UploadOfflineReport(report, overwriteExistingData);
                    }
                    catch
                    {
                        // Making sure that the state is saved persistently, so next time this method is
                        // called, everything will back to normal, with only those that require to be uploaded
                        // No repetition should be guaranteed.
                        SaveOfflineData();
                        return false;
                    }

                    // Already saved or NOT, so no need to keep it.
                    pendingOfflineReports.Remove(report);
                    string key = string.Format("{0}|{1}|{2}|{3}", report.FacilityCode, report.ReportId, report.ReportYear, report.ReportMonth);
                    if (existingPendingOfflineReports.ContainsKey(key)) existingPendingOfflineReports.Remove(key);

                    if (status.Status == ReportingError.NO_ERROR_LABEL) atLeastOneReportSaved = true;
                    else
                    {
                        report.Skipped = true;
                        report.Tag = status;
                        skippedOfflineReports.Add(report);
                    }
                }

                foreach (var r in skippedOfflineReports) AddOfflineReport(r, true);

                // Here we are saving the remaining in-memory reports. Could be none or
                // some which remained[due to occurance of error]  after uploading some.
                SaveOfflineData();
                return atLeastOneReportSaved;
            }
            else throw new InvalidOperationException("Offline Data Reporting Initialization Failed");
        }

        /// <summary>
        /// Save the offline data into the database.
        /// </summary>
        /// <param name="report">Offline Report data</param>
        /// <param name="overwriteExistingData">Overwrite existing data in the server</param>
        /// <returns>True if saved otherwise false</returns>
        /// <exception cref="System.ArgumentNullException">When the report object or its related indicators are null</exception>
        public static ReportingMessage UploadOfflineReport(OfflineReport report, bool overwriteExistingData)
        {
            if (report == null) throw new ArgumentNullException("Report can not be null");
            if (report.Indicators == null) throw new ArgumentNullException("Report indicators object can not be null");

            var indicators = new IQServices.OfflineReportIndicator[report.Indicators.Count];
            for (int i = 0; i < report.Indicators.Count; i++)
            {
                var item = report.Indicators[i];
                var indicator = new IQServices.OfflineReportIndicator
                {
                    Name = item.Name,
                    ShortCode = item.ShortCode,
                    Value = item.Value
                };
                indicators[i] = indicator;
            }

            var offlineReport = new IQServices.OfflineReport
            {
                CreatedByUserId = report.CreatedByUserId,
                CreatedByUserName = report.CreatedByUserName,
                DateSavedOffline = report.DateSavedOffline,
                FacilityCode = report.FacilityCode,
                FacilityName = report.FacilityName,
                Indicators = indicators,
                ReportComments = report.ReportComments,
                ReportId = report.ReportId,
                ReportName = report.ReportName,
                ReportMessage = report.ReportMessage,
                ReportMonth = report.ReportMonth,
                ReportYear = report.ReportYear
            };

            // Here we need to set the webvervice object that will interact with the server directly
            // This is different from the user based on the server URL
            var uploaderService = new IQServices.IQService();
            uploaderService.Url = AppConfiguration.WebServiceURL;
            uploaderService.AuthenticationHeaderValue = new IQServices.AuthenticationHeader();
            uploaderService.AuthenticationHeaderValue.Username = Username;
            uploaderService.AuthenticationHeaderValue.PasswordHash = PasswordHash;
            var status = uploaderService.UploadOfflineReport(offlineReport, overwriteExistingData);

            ReportingMessage output = new ReportingMessage
            {
                Message = status.Message,
                Status = status.Status
            };
            return output;
        }

        public static void FireEvent(OfflineDataCaptureArgs args)
        {
            if (OfflineDataChanged != null) OfflineDataChanged(null, args);
        }
    }
}
